package AFX::ASF;

=head1 NAME

AFX::ASF - ASF Processing

  add_machine()               - Add machine
  add_master()                - Add Site's Master File
  add_node_terminal()         - Add terminal to Node
  check_id()                  - Check specified Identifier
  DESTROY()                   - ASF destructor
  get_id()                    - Return ASF Identifier
  get_list_machine()          - Return list of machines
  get_machine()               - Return ASF machine for specified id and unit
  get_machine_by_ip()         - Return ASF machine for specified node's IP
  get_machine_node()          - Return Machine's node for node's Identifier
  get_machine_by_node_ip()    - Return Machine's node for node's IP
  get_node_terminal()         - Return Node's terminal
  get_version()               - Return ASF Version
  get_xml()                   - Return ASF as XML
  is_updated()                - Check is ASF Updated
  message_already_smf_ip()    - Message: IP address already exists in SMF.
  message_created()           - Message: ASF created
  message_failed_open()       - Message: ASF failed opening
  message_failed_valid()      - Message: ASF failed validation
  message_failed_version()    - Message: ASF with old version
  message_invalid_node()      - Message: Invalid Node specified
  message_invalid_smf()       - Message: SMF not found.
  message_invalid_smf_id()    - Message: Machine's Identifier not specified in SMF line.
  message_invalid_smf_ip()    - Message: Invalid IP address specified in SMF.
  message_invalid_smf_unit()  - Message: Machine unit not specified in SMF line.
  message_machine_added()     - Message: Machine added in ASF.
  message_node_added()        - Message: Node added in ASF.
  message_node_ip_updated()   - Message: Node IP updated in machine
  message_smf_created()       - Message: SMF created
  message_updated()           - Message: ASF updated
  message_uptodate()          - Message: ASF up-to-date
  new()                       - ASF contructor
  open_xml()                  - Open ASF
  open_smf()                  - Open SMF
  parse_smf()                 - Parse SMF
  set_smf()                   - Set SMF
  set_version()               - Set ASF Version
  set_xml()                   - Set XML into ASF
  show_units()                - Set ASF Units details
  update()                    - Update ASF
  update_machine()            - Update machine
  update_machine_node()       - Update node in machine

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2010 Mikhail Volkov

This file is part of AuditFX.

    AuditFX is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    AuditFX is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with AuditFX.  If not, see <http://www.gnu.org/licenses/>.
=cut

use strict;
use Carp;
use Exporter ();

use vars qw($VERSION);
my @ISA = qw(Exporter);
$VERSION = '1.0';

##############################################################################
# Globals
#
use Net::IP;
use Storable qw(dclone);

##############################################################################
#
# add_machine()
#
# Add machine
#
# Return:
# - Machine's Hash   - if added
# - 0                - otherwise
#
# Arguments expected are:
# - self             - Object Reference
# - machine_default  - Default Machine's Hash
# - machine_id       - Machine's Identifier
# - machine_unit     - Machine's unit

sub add_machine {
    my ( $self, $machine_default, $machine_id, $machine_unit ) = @_;

## Check machine exists
    my $machine = $self->get_machine( $machine_id, $machine_unit );

## If Machine already exists return it
    return $machine if $machine;

## Add new machine by cloning it
    $machine = dclone($machine_default);

## Specified Machine's Identifier
    $machine->{id} = $machine_id;

## Delete all default nodes
    delete( $machine->{node} );

## Add machine
    push( @{ $self->{_xml}->{machine} }, $machine );

## If Machine not added, return Error
    if ( !$self->get_machine( $machine->{id}, $machine->{unit} ) ) {

## Return Error
        return 0;
    }

## Print Machine added message
    $self->message_machine_added( $machine_id, $machine_unit );

## Set updated state
    $self->{updated}++;

## Return Machine Hash
    return $machine;

} ## end sub add_machine

##############################################################################
#
# add_master()
#
# Add Site's Master File
#
# Return:
# - 1           - file created/updated
# - 0           - oherwise
#
# Arguments expected are:
# - self        - Object reference
# - site_id     - Site's Identifier

sub add_master {
    my ( $self, $site_id ) = @_;

## Return error, if wrong site_id specified
    return 0 if !$site_id or $site_id eq 'TEST';

## If file already exists
    if ( -f $self->{smf_path} ) {

## Update ASF using SMF
        $self->parse_smf();

## Return Ok
        return 1;
    }

## Define SMF filename
    my $smfile = "TEST.master";

## Define SMF file's content
    my $smf_content = "";

## Open SMF
    if ( open( HOSTS, "< $self->{directory}/$smfile" ) ) {

## Get Sitefile content
        $smf_content = join( '', <HOSTS> );
        close(HOSTS);
        chomp($smf_content);
    }

## Set new site's content
    $self->set_smf($smf_content) if $smf_content;

## Return Ok
    return 1;

} ## end sub add_master

##############################################################################
#
# add_node_terminal()
#
# Add terminal to Node
#
# Return: Terminal Hash
#
# Arguments expected are:
# - self        - Object reference
# - node        - Node Hash
# - terminal_id - Terminal's Identifier

sub add_node_terminal {
    my ( $self, $node, $terminal_id ) = @_;

## Return error, if terminal already exists
    return 0 if $self->get_node_terminal($node);

## Set terminal
    my $terminal = { id => $terminal_id };

## Add terminal into node
    push( @{ $node->{terminal} }, $terminal );

## Set updated state
    $self->{updated}++;

## Return Ok
    return $terminal;

} ## end sub add_node_terminal

##############################################################################
#
# DESTROY()
#
# ASF destructor
#
# Arguments expected are:
# - self         - Object reference

sub DESTROY {
    my ($self) = @_;

##    print "ASF destroyed\n";

## Return Ok
    1;

} ## end sub DESTROY

##############################################################################
#
# check_id()
#
# Check specified Identifier
#
# Return:
# - identifier
#
# Arguments expected are:
# - self    - Object reference
# - id      - Identifier

sub check_id {
    my ( $self, $id ) = @_;

## Return Identifier
    return $id =~ /^[A-Za-z0-9_\-#]+$/;

} ## end sub check_id

##############################################################################
#
# delete_master()
#
# Delete Site's Master File
#
# Return:
# - 1           - file deleted
# - 0           - oherwise
#
# Arguments expected are:
# - self        - Object reference
# - site_id     - Site's Identifier

sub delete_master {
    my ( $self, $site_id ) = @_;

## Return error, if wrong site_id specified
    return 0 if !$site_id or $site_id eq 'TEST';

## Delete Master and XML Files
    unlink("$self->{smf_path}");
    unlink("$self->{path}");

## Return Ok
    return 1;

} ## end sub delete_master

##############################################################################
#
# get_id()
#
# Return ASF Identifier
#
# Return:
# - identifier
#
# Arguments expected are:
# - self    - Object reference

sub get_id {
    my ($self) = @_;

## Return Identifier
    return $self->{_xml}->{id};

} ## end sub get_id

##############################################################################
#
# get_list_machine()
#
# Return:
# - list of all machines
# - 0             - if list empty
#
# Arguments expected are:
# - self          - Object reference
# - machine_unit    - Machine's unit

sub get_list_machine {
    my ( $self, $machine_unit ) = @_;

## Return Error, if no machines found
    return 0 if !$self->{_xml}->{machine};

## If machine's units specified
    if ($machine_unit) {

## Check machines in ASF
        foreach my $machine ( @{ $self->{_xml}->{machine} } ) {

## Skip wrong machine
            next if $machine->{unit} ne $machine_unit;

## Return 1
            return $self->{_xml}->{machine};
        }

## Return Error
        return 0;
    }

## Return Machine list
    return $self->{_xml}->{machine};

} ## end sub get_list_machine

##############################################################################
#
# get_machine()
#
# Return ASF machine for specified id and unit
#
# Return:
# - Machine hash    - it exists in ASF
# - 0               - otherwise
#
# Arguments expected are:
# - self            - Object Reference
# - machine_id      - Machine's id
# - machine_unit    - Machine's unit

sub get_machine {
    my ( $self, $machine_id, $machine_unit ) = @_;

## Return Error, if no machines found
    return 0 if !$self->get_list_machine();

## Check machines in ASF
    foreach my $machine ( @{ $self->get_list_machine() } ) {

## Skip wrong machine
        next
          if $machine->{id} ne $machine_id
              or $machine->{unit} ne $machine_unit;

## Return Machine hash
        return $machine;
    }

## If Machine doesn't exist in ASF, return Error
    return 0;

} ## end sub get_machine

##############################################################################
#
# get_machine_by_node_ip()
#
# Return ASF machine for specified node's IP
#
# Return:
# - Machine hash    - it exists in ASF
# - 0               - otherwise
#
# Arguments expected are:
# - self            - Object Reference
# - node_ip         - Node's IP
# - machine_unit    - Machine's unit

sub get_machine_by_node_ip {
    my ( $self, $node_ip, $machine_unit ) = @_;

## Return Error, if no machines found
    return 0 if !$self->get_list_machine();

## Check machines in ASF
    foreach my $machine ( @{ $self->get_list_machine() } ) {

## Skip wrong machine
        next if $machine->{unit} ne $machine_unit;

## Return Machine hash
        my $node = $self->get_machine_node_by_ip( $machine, $node_ip );

## Return Machine hash if node found
        return $machine if $node;
    }

## If Machine doesn't exist in ASF, return Error
    return 0;

} ## end sub get_machine_by_node_ip

##############################################################################
#
# get_machine_node()
#
# Return Machine's node for node's Identifier
#
# Return:
# - Node hash   - if it exists
# - 0           - otherwise
#
# Arguments expected are:
# - self        - Object reference
# - machine     - Machine hash
# - node_id     - Node's Identifier

sub get_machine_node {
    my ( $self, $machine, $node_id ) = @_;

## Check node in Machine
    foreach my $node ( @{ $machine->{node} } ) {

## Skip wrong node
        next if $node_id ne $node->{host};

## Return Node hash
        return $node;
    }

## If node doesn't exist, return 0
    return 0;

} ## end sub get_machine_node

##############################################################################
#
# get_machine_node_by_ip()
#
# Return Machine's node for node's IP
#
# Return:
# - Node hash   - if it exists
# - 0           - otherwise
#
# Arguments expected are:
# - self        - Object reference
# - machine     - Machine hash
# - node_ip     - Node's IP

sub get_machine_node_by_ip {
    my ( $self, $machine, $node_ip ) = @_;

## Check node in Machine
    foreach my $node ( @{ $machine->{node} } ) {

## Skip wrong node
        next if $node_ip ne $node->{ip};

## Return Node hash
        return $node;
    }

## If node doesn't exist, return 0
    return 0;

} ## end sub get_machine_node_by_ip

##############################################################################
#
# get_version()
#
# Return ASF Version
#
# Return:
# - version
#
# Arguments expected are:
# - self    - Object reference

sub get_version {
    my ($self) = @_;

## Return Version
    return $self->{_xml}->{version};

} ## end sub get_version

##############################################################################
#
# get_node_terminal()
#
# Return Node's terminal
#
# Return:
# - Terminal hash   - if it exists
# - 0               - otherwise
#
# Arguments expected are:
# - self            - Object reference
# - node            - Node hash

sub get_node_terminal {
    my ( $self, $node ) = @_;

## If terminal doesn't exist, return error
    return 0 if !$node->{terminal} or !$node->{terminal}->[0]->{id};

## Return first terminal in Node
    return $node->{terminal}->[0];

} ## end sub get_node_terminal

##############################################################################
#
# get_xml()
#
# Return ASF as XML
#
# Return:
# - xml
#
# Arguments expected are:
# - self    - Object reference

sub get_xml {
    my ($self) = @_;

## Return Site
    return
      eval { $self->{ACF}->{xs}->XMLout( $self->{_xml}, RootName => 'site' ); };

} ## end sub get_xml

##############################################################################
#
# is_updated()
#
# Check is ASF Updated
#
# Return:
# - updated state
#
# Arguments expected are:
# - self    - Object reference

sub is_updated {
    my ($self) = @_;

## Return updated state
    return $self->{updated};

} ## end sub is_updated

##############################################################################
#
# message_already_smf_ip()
#
# Message: IP address already attached in SMF.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self       - Object reference
# - mach_id    - Machine's Id
# - node_ip    - Specified IP address
# - line       - Line number

sub message_already_smf_ip {
    my ( $self, $mach_id, $node_ip, $line ) = @_;

## Set message
    my $message = "SMF line $line skipped: ";
    $message .= "[$node_ip] already attached to $mach_id";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_already_smf_ip

##############################################################################
#
# message_created()
#
# Message: ASF created
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference

sub message_created {
    my ($self) = @_;

## Set message
    my $message = "ASF $self->{file}: created successfully.";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_created

##############################################################################
#
# message_failed_open()
#
# Message: ASF failed opening
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference
# - error     - Error

sub message_failed_open {
    my ( $self, $error ) = @_;

## Set message
    my $message = "ASF $self->{file}: failed opening.";
    $message .= "\nError: $error" if $error;

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_failed_open

##############################################################################
#
# message_failed_valid()
#
# Message: ASF failed validation
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference
# - error     - Error

sub message_failed_valid {
    my ( $self, $error ) = @_;

## Remove newlines in error
    $error =~ s/\n//g;

## Set message
    my $message = "ASF $self->{file}: failed validation.";
    $message .= "\nError: $error" if $error;

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_failed_valid

##############################################################################
#
# message_failed_version()
#
# Message: ASF with old version
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference

sub message_failed_version {
    my ($self) = @_;

## Set message
    my $message = "$self->{id}: ASF was created using another version. ";
    $message .= "Please delete it.";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_failed_version

##############################################################################
#
# message_invalid_node()
#
# Message: Invalid Node specified
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - machine_id   - Machine's Id
# - node_id      - Node's Id

sub message_invalid_node {
    my ( $self, $machine_id, $node_id ) = @_;

## Set message
    my $message = "$self->{id}: ";
    $message .= "invalid $node_id specified in machine $machine_id";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_node

##############################################################################
#
# message_invalid_smf()
#
# Message: SMF not found.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self     - Object reference

sub message_invalid_smf {
    my ($self) = @_;

## Set message
    my $message = "SMF $self->{smfile}: not found";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_smf

##############################################################################
#
# message_invalid_smf_id()
#
# Message: Machine's Identifier not specified in SMF line.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - machine_id   - Machine's Id
# - line         - Line number

sub message_invalid_smf_id {
    my ( $self, $machine_id, $line ) = @_;

## Set message
    my $message = "SMF line $line skipped: ";
    $message .= "invalid machine's identifier $machine_id specified";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_smf_id

##############################################################################
#
# message_invalid_smf_ip()
#
# Message: Invalid IP address specified in SMF.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self       - Object reference
# - node_id    - Node's Id
# - node_ip    - Specified IP address
# - line       - Line number

sub message_invalid_smf_ip {
    my ( $self, $node_id, $node_ip, $line ) = @_;

## Set message
    my $message = "SMF line $line skipped: ";
    $message .= "invalid $node_id [$node_ip] specified";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_smf_ip

##############################################################################
#
# message_invalid_smf_unit()
#
# Message: Machine unit not specified in SMF line.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self          - Object reference
# - machine_unit  - Machine's Unit
# - line          - Line number

sub message_invalid_smf_unit {
    my ( $self, $machine_unit, $line ) = @_;

## Set message
    my $message = "SMF line $line skipped: ";
    $message .= "invalid machine's unit $machine_unit specified";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_smf_unit

##############################################################################
#
# message_machine_added()
#
# Message: Machine added in ASF.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self          - Object reference
# - machine_id    - Machine's Id
# - machine_unit  - Machine's Unit

sub message_machine_added {
    my ( $self, $machine_id, $machine_unit ) = @_;

## Set message
    my $message = "$self->{id}: ";
    $message .= "machine $machine_id [$machine_unit] added";

## Return message
    return $self->{ACF}->logging( $message, 0, 'audit' );

} ## end sub message_machine_added

##############################################################################
#
# message_node_added()
#
# Message: Node added in ASF.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - machine_id   - Machine's Id
# - node_id      - Node's Id
# - node_ip      - Node's IP

sub message_node_added {
    my ( $self, $machine_id, $node_id, $node_ip ) = @_;

## Set message
    my $message = "$self->{id}: ";
    $message .= "node $node_id [$node_ip] attached to $machine_id";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_node_added

##############################################################################
#
# message_node_ip_updated()
#
# Message: Node IP updated in machine
#
# Return:
# - logged message
#
# Arguments expected are:
# - self          - Object reference
# - machine_id    - Machine's Id
# - node_id       - Node's Id
# - node_ip       - Node's IP

sub message_node_ip_updated {
    my ( $self, $machine_id, $node_id, $node_ip ) = @_;

## Set message
    my $message = "$self->{id}: ";
    $message .= "node $node_id [$node_ip] updated in machine $machine_id";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_node_ip_updated

##############################################################################
#
# message_smf_created()
#
# Message: SMF created
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference

sub message_smf_created {
    my ($self) = @_;

## Set message
    my $message = "SMF $self->{smfile}: created successfully";
    $message .= "\nPlease fill it according to the site configuration.";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_smf_created

##############################################################################
#
# message_updated()
#
# Message: ASF updated
#
# Return:
# - logged message
#
# Arguments expected are:
# - self          - Object reference

sub message_updated {
    my ($self) = @_;

## Set message
    my $message = "ASF $self->{file}: updated successfully";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_updated

##############################################################################
#
# message_uptodate()
#
# Message: ASF up-to-date
#
# Return:
# - logged message
#
# Arguments expected are:
# - self          - Object reference

sub message_uptodate {
    my ($self) = @_;

## Set message
    my $message = "$self->{id}: up-to-date";

## Return message
    return $self->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_uptodate

##############################################################################
#
# new()
#
# ASF contructor
#
# Arguments expected are:
# - ACF Hash
# - Site's filename or Identifier

sub new {

## Get package name
    my $type = shift;

## Define object's hash
    my $self = { ACF => shift, id => shift, updated => 0 };

## If Site Identifier
    if ( !$self->{id} ) {

## Return Error
        return 0;
    }

## If specified Site's Identifier
    if ( $self->{id} !~ /\.xml$/ ) {
        $self->{file} = $self->{id} . ".xml";
    }
    else {
        $self->{file} = $self->{id};
    }

## Get directory
    $self->{directory} = $self->{ACF}->get_directory('site');

## Set full pathname
    $self->{path} = $self->{directory} . "/" . $self->{file};

## If failed opening, create new
    if ( !-f $self->{path} ) {

## Return new ASF Hash
        $self->{_xml} =
          { id => $self->{id}, version => $self->{ACF}->get_version() };

## Save ASF
        eval {
            $self->{ACF}->{xs}->XMLout(
                $self->{_xml},
                RootName   => 'site',
                OutputFile => $self->{path}
            );
        };

## Print ASF created message
        message_created($self);
    }

## Open ASF
    return 0 if !open_xml($self);

## Check ASF version
    if (  !get_version($self)
        or get_version($self) ne $self->{ACF}->get_version() )
    {

## Print failed version error
        message_failed_version($self);

## Return Error
        return 0;
    }

## Define smf filename
    $self->{smfile} = $self->{id} . ".master";

## Set full pathname to SMF
    $self->{smf_path} = $self->{directory} . "/" . $self->{smfile};

## Return object
    return bless $self, $type;

} ## end sub new

##############################################################################
#
# open_xml()
#
# Open ASF
#
# Return:
# - 1             - if opened successfully
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference

sub open_xml {
    my ($self) = @_;

## If failed opening
    if ( !-f $self->{path} ) {

## Print failed openning error
        $self->message_failed_open();

## Return error
        return 0;
    }

## Validate ASF
    my $error = $self->{ACF}->validate_file( $self->{path} );

## Exit if ASF failed validation
    if ($error) {

## Print failed validation error
        message_failed_valid( $self, $error );

## Return Error
        return 0;
    }

## Open ASF
    $self->{_xml} = eval { $self->{ACF}->{xs}->XMLin( $self->{path} ); };

## Exit if ASF failed opening
    if ( $@ or !$self->{_xml} or !$self->{_xml}->{id} ) {

## Print failed openning error
        message_failed_open( $self, "$@" );

## Return Error
        return 0;
    }

## Redefine Site's Identifier in Object
    $self->{id} = get_id($self);

## Remove updated state
    $self->{updated} = 0;

## Return Ok
    return 1;

} ## end sub open

##############################################################################
#
# open_smf()
#
# Open SMF
#
# Return:
# - 1        - if opened successfully
# - 0        - otherwise
#
# Arguments expected are:
# - self     - Object reference

sub open_smf {
    my ($self) = @_;

## Open SMF
    if ( !open( HOSTS, "< $self->{smf_path}" ) ) {

## If SMF not found, return 0
        $self->message_invalid_smf();

## Return Error
        return 0;
    }

## Get SMF content
    my $content = join( '', <HOSTS> );
    close(HOSTS);
    chomp($content);

## Return Static
    return $content;

} ## end sub open_smf

##############################################################################
#
# parse_smf()
#
# Parse SMF
#
# Return:
# - 1        - if parsed successfully
# - 0        - otherwise
#
# Arguments expected are:
# - self     - Object reference
# - noprompt - Set no prompt mode

sub parse_smf {
    my ( $self, $noprompt ) = @_;

## Open SMF
    if ( !open( HOSTS, "< $self->{smf_path}" ) ) {

## If SMF not found, return 0
        $self->message_invalid_smf();

## Return Error
        return 0;
    }

## Set line number
    my $line = 0;

## Check all hosts in SMF
    foreach my $hosts (<HOSTS>) {

## Increment line counter
        $line++;

## Ignore lines with comments and blank
        next if $hosts =~ /^#/ or $hosts =~ /^\s*$/;
        chomp($hosts);

## Split line by whitespaces
        my @host = split( /[\s\t]+/, $hosts );

## Check IP address (for IPv4 only)
        my $node_ip = shift @host;

## Node's identifier is first value in Lower case
        my $node_id = lc( shift @host );

## Check specified IP address
        if (   !Net::IP::ip_is_ipv4($node_ip)
            or $node_ip eq '0.0.0.0'
            or !$node_id
            or !$self->check_id($node_id) )
        {

## If invalid IP specified print message
            $self->message_invalid_smf_ip( $node_id, $node_ip, $line );

## Goto next host
            next;
        }

## Define machine variables
        my $machine_unit = "";
        my $machine_id   = "";

## Check parameters in hosts
        foreach my $param (@host) {

## Find MACH/UNIT for unit in Upper Case
            if ( $param =~ m/(?:MACH|UNIT)=(.*)$/i ) {
                $machine_unit = uc($1);
            }

## Find MACH_ID/ID in Upper Case
            if ( $param =~ m/(?:MACH_ID|ID)=(.*)$/i ) {
                $machine_id = uc($1);
            }
        }

## Exception: No Machine unit specified
        if ( !$machine_unit or !$self->check_id($machine_unit) ) {

## Print SMF no unit specified message
            $self->message_invalid_smf_unit( $machine_unit, $line );

## Goto next line
            next;
        }    ## Exception

## Exception:: No Machine id specified
        if ( !$machine_id or !$self->check_id($machine_id) ) {

## Print SMF no id specified message
            $self->message_invalid_smf_id( $machine_id, $line );

## Goto next line
            next;
        }    ## Exception

## Get default machine from ACF
        my $machine_default = $self->{ACF}->get_machine($machine_unit);

## Exception:: No machine found in ACF
        if ( !$machine_default or !$machine_default->{id} ) {

## Print SMF invalid unit specified message
            $self->message_invalid_smf_unit( $machine_unit, $line );

## Goto next line
            next;
        }    ## Exception

## Check machine by node's IP
        my $mach_ip = $self->get_machine_by_node_ip( $node_ip, $machine_unit );

## If node with IP already exist
        if ( $mach_ip and $mach_ip->{id} and $mach_ip->{id} ne $machine_id ) {

## If invalid IP specified print message
            $self->message_already_smf_ip( $mach_ip->{id}, $node_ip, $line );

## Goto next host
            next;
        }

## Get machine from ASF
        my $machine = $self->get_machine( $machine_id, $machine_unit );

## If machine doesn't exist in ASF
        if ( !$machine or !$machine->{id} ) {

## Add machine in ASF
            $machine =
              $self->add_machine( $machine_default, $machine_id,
                $machine_unit );
        }

## If machine invalid
        if ( !$machine or !$machine->{id} ) {

## Print SMF invalid unit specified message
            $self->message_invalid_smf_unit( $machine_unit, $line );

## Goto next line
            next;
        }

## Update node in machine
        $self->update_machine_node( $machine, $machine_default, $node_id,
            $node_ip );
    }

## Close SMF
    close(HOSTS);

## If ASF was updated
    if ( $self->is_updated() ) {

## Print ASF saving confirm
        print "\nDO YOU WANT TO SAVE CHANGES FOR "
          . $self->get_id()
          . "?[Y/N] ";

## Require user input to continue
        chomp( my $yes = <STDIN> ) if !$noprompt;

## Create new ASF
        $self->update() if uc($yes) eq 'Y' or $noprompt;
    }

## Return Ok
    return 1;

} ## end sub parse_smf

##############################################################################
#
# set_smf()
#
# Set SMF
#
# Return:
# - 1             - if set successfully
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference
# - smf_content   - SMFile's content

sub set_smf {
    my ( $self, $smf_content ) = @_;

## Open SMF
    if ( !open( HOSTS, "> $self->{smf_path}" ) ) {

## If SMF not found, return 0
        $self->message_invalid_smf();

## Return Error
        return 0;
    }

## Set SMFile content
    print HOSTS $smf_content;

## Close SMF
    close(HOSTS);

## Print ASF created message
    message_smf_created($self);

## Return Ok
    return 1;

} ## end sub set_smf

##############################################################################
#
# set_version()
#
# Set ASF Version
#
# Return:
# - 1
#
# Arguments expected are:
# - self    - Object reference

sub set_version {
    my ($self) = @_;

## Set Version
    $self->{_xml}->{version} = $self->{ACF}->get_version();

## Return Ok
    return 1;

} ## end sub set_version

##############################################################################
#
# set_xml()
#
# Set XML into ASF
#
# Return:
# - 1       - if xml set
# - 0       - if error happend
#
# Arguments expected are:
# - self    - Object reference
# - xml     - XML

sub set_xml {
    my ( $self, $xml ) = @_;

## Validate ASF
    my $error = $self->{ACF}->validate_string($xml);

## If ASF failed validation, return Error
    if ($error) {

## Print ASF failed validation
        message_failed_valid( $self, $error );

## Return Error
        return 0;
    }

## Open ASF
    $self->{_xml} = eval { $self->{ACF}->{xs}->XMLin($xml); };

## If ASF failed openning return 0
    if ( $@ or !$self->{_xml} or !$self->{_xml}->{id} ) {

## Print ASF failed openning
        message_failed_open( $self, "$@" );

## Return Error
        return 0;
    }

## Return Ok
    return 1;

} ## end sub set_xml

##############################################################################
#
# show_units()
#
# Show ASF Units details
#
# Return:
# - 1         - if continue and units exists
# - 0         - otherwise
#
# Arguments expected are:
# - self    - Object reference

sub show_units {
    my ($self) = @_;

## Return Error, if no machines found
    return 0 if !$self->{ACF}->get_list_machine();

## Sort as units in ACF
    foreach my $acf_machine ( @{ $self->{ACF}->get_list_machine() } ) {

## Goto next ASF if no machines found
        next if !$self->get_list_machine( $acf_machine->{unit} );

## Show machine
        print "\n-u $acf_machine->{id}: ";

## Processing Machine
        foreach my $machine ( @{ $self->get_list_machine() } ) {

## Skip machine with wrong unit
            next if $machine->{unit} ne $acf_machine->{unit};

## Show machine
            print "$machine->{id}, ";
        }

## Print next line
        print "\n";
    }

## Return Ok
    return 1;

} ## end sub show_units

##############################################################################
#
# update()
#
# Update ASF
#
# Return:
# - 1         - if updated sucessfully
# - 0         - otherwise
#
# Arguments expected are:
# - self      - Object reference

sub update {
    my ($self) = @_;

## If ASF not Updated
    if ( !$self->{updated} ) {

## Print message: ASF up-to-date
        $self->message_uptodate();

## Return Error
        return 0;
    }

## Set version
    $self->set_version();

## Save ASF
    eval {
        $self->{ACF}->{xs}->XMLout(
            $self->{_xml},
            RootName   => 'site',
            OutputFile => $self->{path}
        );
    };

## Validate ASF
    my $error = $self->{ACF}->validate_file( $self->{path} );

## Exit if ASF failed validation
    if ($error) {

## Print failed validation error
        $self->message_failed_valid($error);

## Return Error
        return 0;
    }

## Empty update
    $self->{updated} = 0;

## Print ASF created message
    $self->message_updated();

## Return Ok
    return 1;

} ## end sub update

##############################################################################
#
# update_machine()
#
# Update machine
#
# Return:
# - 1                - if machine updated successfully
# - 0                - otherwise
#
# Arguments expected are:
# - self             - Object reference
# - machine          - Machine hash

sub update_machine {
    my ( $self, $arf_machine ) = @_;

## Get machine
    my $machine =
      $self->get_machine( $arf_machine->{id}, $arf_machine->{unit} );

## Return Error, if machine not found
    return 0 if !$machine or !$machine->{id};

## Copy updated passwords into ASF's machine
    if ( $arf_machine->{passwd} ) {
        $machine->{passwd} = $arf_machine->{passwd};
    }

## Check templates in Machine
    foreach my $template ( @{ $arf_machine->{template} } ) {

## Return Template Hash
        delete( $template->{valid} );
    }

## Copy updated templates into ASF's machine
    if ( $arf_machine->{template} ) {
        $machine->{template} = $arf_machine->{template};
    }

## Copy updated node into ASF's machine
    if ( $arf_machine->{node} ) {
        $machine->{node} = $arf_machine->{node};
    }

## Set updated state
    $self->{updated}++;

## Return Ok
    return 1;

} ## end sub update_machine

##############################################################################
#
# update_machine_node()
#
# Update node in machine
#
# Return:
# - 1                - if Machine updated successfully
# - 0                - otherwise
#
# Arguments expected are:
# - self             - Object reference
# - machine          - Machine hash
# - machine_default  - Machine default hash from ACF
# - node_id          - Node id
# - node_ip          - Node IP address

sub update_machine_node {
    my ( $self, $machine, $machine_default, $node_id, $node_ip ) = @_;

## Find exists node in machine
    my $node = $self->get_machine_node( $machine, $node_id );

## If node already in machine
    if ($node) {

## If IP doesn't match, replace with new one
        if ( $node->{ip} ne $node_ip ) {

## Set node's IP
            $node->{ip} = $node_ip;

## Print Node IP updated message
            $self->message_node_ip_updated( $machine->{id}, $node_id,
                $node_ip );

## Set updated state
            $self->{updated}++;
        }

## Return Ok
        return 1;
    }

## Get default node for machine
    my $default_node = $self->get_machine_node( $machine_default, 'default' );

## Exception: No default node found
    if ( !$default_node ) {

## Print invalid node message
        $self->message_invalid_node( $machine_default->{id}, 'default' );

## Return Error
        return 0;
    }    ## Exception

## Clonning default node
    $node = dclone($default_node);

## Set node host and IP
    $node->{host} = $node_id;
    $node->{ip}   = $node_ip;

## Add node to Machine
    push( @{ $machine->{node} }, $node );

## If node not added
    if ( !$self->get_machine_node( $machine, $node_id ) ) {

## Print invalid node message
        $self->message_invalid_node( $machine->{id}, $node_id );

## Return Error
        return 0;
    }

## Print Node added message
    $self->message_node_added( $machine->{id}, $node_id, $node_ip );

## Set updated state
    $self->{updated}++;

## Return Ok
    return 1;

} ## end sub update_machine_node

1;
